<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="迎着朝阳的博客" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    MyBatis体系结构 |  迎着朝阳
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="https://dxysun.com/static/yan.png" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  
<script>
var _hmt = _hmt || [];
(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?aa994a8d65700b8835787dd39d079d7e";
	var s = document.getElementsByTagName("script")[0]; 
	s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-mybatisForSource"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  MyBatis体系结构
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/05/23/mybatisForSource/" class="article-date">
  <time datetime="2021-05-23T08:40:24.000Z" itemprop="datePublished">2021-05-23</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/mybatis/">mybatis</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">3.4k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">12 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p>MyBatis体系结构</p>
<a id="more"></a>



<h1 id="MyBatis的工作流程"><a href="#MyBatis的工作流程" class="headerlink" title="MyBatis的工作流程"></a>MyBatis的工作流程</h1><p>MyBatis的主要工作流程</p>
<h2 id="解析配置文件"><a href="#解析配置文件" class="headerlink" title="解析配置文件"></a>解析配置文件</h2><p>首先在MyBatis启动的时候我们要去解析配置文件，包括全局配置文件和映射器配置文件，这里面包含了怎么控制MyBatis的行为，和要对数据库下达的指令，也就是SQL信息，mybatis会把它们解析成一个Configuration对象。</p>
<h2 id="提供操作接口"><a href="#提供操作接口" class="headerlink" title="提供操作接口"></a>提供操作接口</h2><p>接下来就是操作数据库的接口，它在应用程序和数据库中间，代表我们跟数据库之间的一次连接，这个就是SqlSession对象，要获得一个会话，必须有一个会话工厂SqlSessionFactory。SqlSessionFactory里面又必须包含我们的所有的配置信息，所以我们会通过一个Builder来创建工厂类。</p>
<p>MyBatis是对JDBC的封装，也就是意味着底层一定会出现JDBC的一些核心对象，比如执行SQL的Statement，结果集ResultSet。在Mybatis里面，SqlSession只是提供给应用的一个接口，还不是SQL的真正的执行对象。</p>
<h2 id="执行SQL操作"><a href="#执行SQL操作" class="headerlink" title="执行SQL操作"></a>执行SQL操作</h2><p>SqlSession持有了一个Executor对象，用来封装对数据库的操作。</p>
<p>在执行器Executor执行query或者update操作的时候mybatis创建一系列的对象，来处理参数、执行SQL、处理结果集，这里mybatis把它简化成一个对象：StatementHandler，可以把它理解为对Statement的封装。</p>
<p>下面就是MyBatis主要的工作流程，如图</p>
<p><img src="https://tu.dxysun.com/image-20210523171606068-20210523171607.png" alt="image-20210523171606068"></p>
<h1 id="MyBatis架构分层与模块划分"><a href="#MyBatis架构分层与模块划分" class="headerlink" title="MyBatis架构分层与模块划分"></a>MyBatis架构分层与模块划分</h1><p>在MyBatis的主要工作流程里面，不同的功能是由很多不同的类协作完成的，它们分布在MyBatis jar包的不同的package里面。</p>
<p>按照功能职责的不同，所有的package可以分成不同的工作层次。</p>
<p><img src="https://tu.dxysun.com/image-20210523171844325-20210523171846.png" alt="image-20210523171844325"></p>
<h2 id="接口层"><a href="#接口层" class="headerlink" title="接口层"></a>接口层</h2><p>首先接口层是我们打交道最多的。核心对象是SqlSession，它是上层应用和MyBatis打交道的桥梁，SqlSession上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候，会调用核心处理层的相应模块来完成具体的数据库操作。</p>
<h2 id="核心处理层"><a href="#核心处理层" class="headerlink" title="核心处理层"></a>核心处理层</h2><p>接下来是核心处理层。既然叫核心处理层，也就是跟数据库操作相关的动作都是在这一层完成的。</p>
<p>核心处理层主要做了这几件事：</p>
<p>1、把接口中传入的参数解析并且映射成JDBC类型；</p>
<p>2解析xml文件中的SQL语句，包括插入参数，和动态SQL的生成；</p>
<p>3执行SQL语句；</p>
<p>4、处理结果集，并映射成Java对象。</p>
<p>插件也属于核心层，这是由它的工作方式和拦截的对象决定的。</p>
<h2 id="基础支持层"><a href="#基础支持层" class="headerlink" title="基础支持层"></a>基础支持层</h2><p>最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能（实现复用），用来支持核心处理层的功能。比如数据源、缓存、日志、xml解析、反射、I0、事务等等这些功能。</p>
<h1 id="MyBatis缓存详解"><a href="#MyBatis缓存详解" class="headerlink" title="MyBatis缓存详解"></a>MyBatis缓存详解</h1><p>缓存是一般的ORM框架都会提供的功能，目的就是提升查询的效率和减少数据库的压力。跟Hibernate一样，MyBatis也有一级缓存和二级缓存，并且预留了集成第三方缓存的接口。</p>
<h2 id="缓存体系结构"><a href="#缓存体系结构" class="headerlink" title="缓存体系结构"></a>缓存体系结构</h2><p>MyBatis跟缓存相关的类都在cache包里面，其中有一个Cache接口，只有一个默认的实现类PerpetualCache，它是用HashMap实现的。</p>
<p>PerpetualCache这个对象一定会创建，所以这个叫做基础缓存。但是缓存又可以有很多额外的功能，比如回收策略、日志记录、定时刷新等等，如果需要的话，就可以给基础缓存加上这些功能，如果不需要，就不加。</p>
<p>除了基础缓存之外，MyBatis也定义了很多的装饰器，同样实现了Cache接口，通过这些装饰器可以额外实现很多的功能。</p>
<h2 id="一级缓存"><a href="#一级缓存" class="headerlink" title="一级缓存"></a>一级缓存</h2><p>一级缓存也叫本地缓存（LocalCache），MyBatis的一级缓存是在会话（SqlSession）层面进行缓存的。MyBatis的一级缓存是默认开启的，不需要任何的配置（localCacheScope设置为STATEMENT关闭一级缓存）。</p>
<p>首先我们必须去弄清楚一个问题，在MyBatis执行的流程里面，涉及到这么多的对象，那么缓存PerpetualCache应该放在哪个对象里面去维护？</p>
<p>如果要在同一个会话里面共享一级缓存，最好的办法是在SqlSession里面创建的，作为SqlSession的一个属性，跟SqlSession共存亡，这样就不需要为SqISession编号、再根据SqlSession的编号去查找对应的缓存了。</p>
<p>DefaultSqlSession里面只有两个对象属性：Configuration和Executor。</p>
<p>Configuration是全局的，不属于SqlSession，所以缓存只可能放在Executor里面维护实际上它是在基本执行器SimpleExecutor/ReuseExecutor/BatchExecutor的父类BaseExecutor的构造函数中持有了PerpetualCache。</p>
<p>在同一个会话里面，多次执行相同的SQL语句，会直接从内存取到缓存的结果，不会再发送SQL到数据库。但是不同的会话里面，即使执行的SQL一模一样（通过Mapper的同一个方法的相同参数调用），也不能使用到一级缓存。</p>
<p>一级缓存什么时候会被清空呢？</p>
<p>同一个会话中，update（包括delete）会导致一级缓存被清空。</p>
<p>只有更新会清空缓存吗？查询会清空缓存吗？如果要清空呢？</p>
<p>一级缓存是在BaseExecutor中的update() 方法中调用clearLocalCache() 清空的（无条件）</p>
<p>如果是query会判断（只有select标签的fushCache=true才清空）</p>
<p>一级缓存的工作范围是一个会话。如果跨会话，会出现什么问题？</p>
<p>其他会话更新了数据，导致当前会话读取到的是过时的数据（一级缓存不能跨会话共享）</p>
<p><img src="https://tu.dxysun.com/image-20210523184158780-20210523184200.png" alt="image-20210523184158780"></p>
<p>一级缓存的不足</p>
<p>使用一级缓存的时候，因为缓存不能跨会话共享，不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下，会存在查到过时数据的问题。如果要解决这个问题，就要用到工作范围更广的二级缓存。</p>
<h2 id="二级缓存"><a href="#二级缓存" class="headerlink" title="二级缓存"></a>二级缓存</h2><p>二级缓存是用来解决一级缓存不能跨会话共享的问题的，范围是namespace级别的，可以被多个SqlSession共享（只要是同一个接口里面的相同方法，都可以共享）生命周期和应用同步。</p>
<p>思考一个问题：如果开启了二级缓存，二级缓存应该是工作在一级缓存之前，还是在一级缓存之后呢？二级缓存是在哪里维护的呢？</p>
<p>作为一个作用范围更广的缓存，它肯定是在SqlSession的外层，否则不可能被多个SqlSession共享。</p>
<p>而一级缓存是在SqlSession内部的，所以第一个问题，肯定是工作在一级缓存之前，也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。</p>
<p>第二个问题，二级缓存放在哪个对象中维护呢？要跨会话共享的话，SqlSession本身和它里面的BaseExecutor已经满足不了需求了，那我们应该在BaseExecutor之外创建一个对象。</p>
<p>但是，二级缓存是不一定开启的。也就是说，开启了二级缓存，就启用这个对象，如果没有开启，就不用这个对象，我们应该怎么做呢？</p>
<p>实际上MyBatis用了一个装饰器的类来维护，就是CachingExecutor。如果启用了二级缓存，MyBatis在创建Executor对象的时候会对Executor进行装饰。</p>
<p>CachingExecutor对于查询请求，会判断二级缓存是否有缓存结果，如果有就直接返回，如果没有委派交给真正的查询器Executor实现类，比如SimpleExecutor来执行查询，再走到一级缓存的流程。最后会把结果缓存起来，并且返回给用户。</p>
<p><img src="https://tu.dxysun.com/image-20210523184221661-20210523184222.png" alt="image-20210523184221661"></p>
<h3 id="开启二级缓存的方法"><a href="#开启二级缓存的方法" class="headerlink" title="开启二级缓存的方法"></a>开启二级缓存的方法</h3><p>第一步：在mybatis-config.xml中配置了（可以不配置，默认是true）：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;setting name&#x3D;&quot;cacheEnabled&quot; value&#x3D;&quot;true&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure>

<p>只要没有显式地设置<code>cacheEnabled=false</code>，都会用CachingExecutor装饰基本的执行器（SIMPLE、REUSE、BATCH）。</p>
<p>二级缓存的总开关是默认开启的。但是每个Mapper的二级缓存开关是默认关闭的,一个Mapper要用到二级缓存，还要单独打开它自己的开关。</p>
<p>第二步：在Mapper.xml中配置<code>&lt;cache&gt;</code>标签</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache</span> <span class="attr">type</span>=<span class="string">"org.apache.ibatis.cache.impl.PerpetualCache"</span></span></span><br><span class="line">size="1024"  &lt;!-- 最多缓存对象个数，默认1024 --&gt;</span><br><span class="line">eviction="LRU" <span class="comment">&lt;!-- 回收策 --&gt;</span></span><br><span class="line">flushInterval="120000" <span class="comment">&lt;!-- 自动刷新时间ms，未配置时只有调用时刷新 --&gt;</span></span><br><span class="line">readOnly="false" /&gt; <span class="comment">&lt;!-- 默认是false（安全），改为true可读写时，对象必须支持序列化 --&gt;</span></span><br></pre></td></tr></table></figure>
<p>cache属性详解：</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>含义</th>
<th>取值</th>
</tr>
</thead>
<tbody><tr>
<td>type</td>
<td>缓存实现类</td>
<td>需要实现Cache接口，默认是PerpetualCache，可以使用第三方缓存</td>
</tr>
<tr>
<td>size</td>
<td>最多缓存对象个数</td>
<td>默认1024</td>
</tr>
<tr>
<td>eviction</td>
<td>回收策略（缓存淘汰算法）</td>
<td>LRU-最近最少使用的：移除最长时间不被使用的对象（默认）；<br>FIF0-先进先出：按对象进入缓存的顺序来移除它们；<br>SOFT-软引用：移除基于垃圾回收器状态和软引用规则的对象；<br>WEAK-弱引用：更积极的移除基于垃圾回收器状态和软引用规则的对象。</td>
</tr>
<tr>
<td>readOnly</td>
<td>是否只读</td>
<td>true：只读缓存；会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。<br/>false：读写缓存；会返回缓存对象的拷贝（通过序列化），不会共享。这会慢一些，但是安全，因此默认是false。<br>改为false可读写时，对象必须支持序列化。</td>
</tr>
<tr>
<td>blocking</td>
<td>启用阻塞缓存</td>
<td>通过在get/put方式中加锁，保证只有一个线程操作缓存，基于Java重入锁实现</td>
</tr>
</tbody></table>
<p>Mapper.xml配置了<code>&lt;cache&gt;</code>之后，select()会被缓存，update()、delete()、insert()会刷新缓存。</p>
<p>如果二级缓存拿到结果了，就直接返回（最外层的判断），否则再到一级缓存，最后到数据库。</p>
<p>如果cacheEnabled=true，Mapper.xml没有配置<code>&lt;cache&gt;</code>标签，还有二级缓存吗？还会出现CachingExecutor包装对象吗？</p>
<p>只要cacheEnabled=true基本执行器就会被装饰。有没有配置<code>&lt;cache&gt;</code>，决定了在启动的时候会不会创建这个mapper的Cache对象，最终会影响到CachingExecutor query方法里面的判断，也就是说，此时会装饰，但是没有cache对象，依然不会走二级缓存流程。</p>
<p>如果一个Mapper需要开启二级缓存，但是这个里面的某些查询方法对数据的实时性要求很高，不需要二级缓存，怎么办？</p>
<p>我们可以在单个Statement ID上显式关闭二级缓存（默认是true）</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectBlog"</span> <span class="attr">resultMap</span>=<span class="string">"BaseResultMap"</span> <span class="attr">useCache</span>=<span class="string">"false"</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>事务不提交，二级缓存是不生效的</p>
<p>因为二级缓存使用TransactionalCacheManager （TCM）来管理，最后又调用了 TransactionalCache的get0bject()、putObject()和commit()方法，TransactionalCache 里面又持有了真正的Cache对象，比如是经过层层装饰的PerpetualCache。</p>
<p>在putObject的时候，只是添加到了entriesToAddOnCommit里面，只有它的commit() 方法被调用的时候才会调用flushPendingEntries() 真正写入缓存。它就是在DefaultSqISession调用commit() 的时候被调用的。</p>
<p>什么时候开启二级缓存？</p>
<p>一级缓存默认是打开的，二级缓存需要配置才可以开启。那么我们必须思考一个问题，在什么情况下才有必要去开启二级缓存？</p>
<p>1、因为所有的增删改都会刷新二级缓存，导致二级缓存失效，所以适合在查询为主的应用中使用，比如历史交易、历史订单的查询，否则缓存就失去了意义。</p>
<p>2、如果多个namespace中有针对于同一个表的操作，比如Blog表，如果在namespace中刷新了缓存，另一个namespace中没有刷新，就会出现读到脏数据的情况。</p>
<p>所以，推荐在一个Mapper里面只操作单表的情况使用。</p>
<p>如果要让多个namespace共享一个三级缓存，应该怎么做？</p>
<p>跨namespace的缓存共享的问题，可以使用<code>&lt;cache-ref&gt;</code>来解决</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache-ref</span> <span class="attr">namespace</span>=<span class="string">"com.crud.dao.DepartmentMapper"</span>/&gt;</span></span><br></pre></td></tr></table></figure>
<p>cache-ref代表引用别的命名空间的Cache配置，两个命名空间的操作使用的是同个Cache。在关联的表比较少，或者按照业务可以对表进行分组的时候可以使用。</p>
<p>注意：在这种情况下，多个Mapper的操作都会引起缓存刷新，缓存的意义已经不大了。</p>
<h2 id="第三方缓存做二级缓存"><a href="#第三方缓存做二级缓存" class="headerlink" title="第三方缓存做二级缓存"></a>第三方缓存做二级缓存</h2><p>MyBatis自带的二级缓存之外，我们也可以通过实现Cache接口来自定义二级缓存。</p>
<p>MyBatis官方提供了一些第三方缓存集成方式，比如ehcache和redis：</p>
<p><a href="https://github.com/mybatis/redis-cache" target="_blank" rel="noopener">https://github.com/mybatis/redis-cache</a></p>
<p>pom文件引入依赖：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>X</span><br><span class="line">  <span class="tag">&lt;<span class="name">groupld</span>&gt;</span>org.mybatis.caches<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span> </span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactld</span>&gt;</span>mybatis-redis<span class="tag">&lt;/<span class="name">artifactld</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-beta2<span class="tag">&lt;/<span class="name">version</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>Mapper.xml配置，type使用RedisCache：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache</span> <span class="attr">type</span>=<span class="string">"org.mybatis.caches.redis.RedisCache"</span></span></span><br><span class="line"><span class="tag"><span class="attr">eviction</span>=<span class="string">"FIFO"</span> <span class="attr">flushInterval</span>=<span class="string">"60000"</span> <span class="attr">size</span>=<span class="string">"512"</span> <span class="attr">readOnly</span>=<span class="string">"true"</span>/&gt;</span></span><br></pre></td></tr></table></figure>

<p>redis.properties配置</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">host</span>=<span class="string">localhost</span></span><br><span class="line"><span class="attr">port</span>=<span class="string">6379</span></span><br><span class="line"><span class="attr">connectionTimeout</span>=<span class="string">5000</span></span><br><span class="line"><span class="attr">soTimeout</span>=<span class="string">5000</span></span><br><span class="line"><span class="attr">database</span>=<span class="string">0</span></span><br></pre></td></tr></table></figure>



 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://dxysun.com/2021/05/23/mybatisForSource/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mybatis/" rel="tag">mybatis</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/05/23/mybatisForSource1/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            MyBatis源码解读(一)配置加载
          
        </div>
      </a>
    
    
      <a href="/2021/05/23/mybatisForSpring/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">mybatis与Spring整合分析</div>
      </a>
    
  </nav>

  
   
  
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2024
        <i class="ri-heart-fill heart_icon"></i> dxysun
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="https://beian.miit.gov.cn" target="_black" rel="nofollow">豫ICP备17012675号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="https://dxysun.com/static/logo.png" alt="迎着朝阳"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/photos">相册</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/alipay-20201219151322.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/weixin-20201219151346.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
  var ayerConfig = {
    mathjax: true
  }
</script>

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


<script src="/js/dz.js"></script>



    
  </div>
</body>

</html>